package org.asiainfo.gateway.service.impl;

import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.spring.application.ImageCaptchaApplication;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import org.asiainfo.common.core.constant.CacheConstants;
import org.asiainfo.common.core.domain.R;
import org.asiainfo.common.core.exception.user.UserException;
import org.asiainfo.common.core.utils.StringUtils;
import org.asiainfo.common.redis.utils.RedisUtils;
import org.asiainfo.gateway.config.properties.CaptchaProperties;
import org.asiainfo.gateway.form.CaptchaTrackBody;
import org.asiainfo.gateway.service.IValidateCodeService;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.server.ServerRequest;

import java.net.InetSocketAddress;
import java.time.Duration;

/**
 * 验证码实现接口
 *
 * @author dotor-ww
 */
@Service
@RequiredArgsConstructor
public class ValidateCodeServiceImpl implements IValidateCodeService {

    private final CaptchaProperties captchaProperties;

    private final ImageCaptchaApplication imageCaptchaApplication;

    /**
     * 获取验证码状态
     *
     * @param serverRequest 请求
     * @return 结果
     */
    @Override
    public R<Object> getCaptchaStatus(ServerRequest serverRequest) {
        //获取验证码状态
        Boolean captchaEnabled = captchaProperties.getEnabled();
        return R.ok(Dict.create().set("captchaEnabled", captchaEnabled));
    }

    /**
     * 生成验证码
     *
     * @return 结果
     */
    @Override
    public R<Object> createCaptcha(ServerRequest serverRequest) {
        Boolean captchaEnabled = captchaProperties.getEnabled();
        //是否开启验证码
        if (!captchaEnabled) {
            return R.fail("生成验证码失败，验证码未开启！");
        }
        //获取验证码 校验
        validateGet(serverRequest);
        //获取验证码类型
        String type = captchaProperties.getType();
        if (StringUtils.isEmpty(type)) {
            type = CaptchaTypeConstant.SLIDER;
        }
        //创建验证码
        return R.ok(imageCaptchaApplication.generateCaptcha(type));
    }

    /**
     * 校验验证码
     *
     * @param serverRequest 请求
     * @param body          校验验证码
     * @return 结果
     */
    @Override
    public R<Object> checkCaptcha(ServerRequest serverRequest, CaptchaTrackBody body) {
        // 校验验证码接口限流
        validateCheck(serverRequest);
        boolean success = imageCaptchaApplication.matching(body.getId(), body.getImageCaptchaTrack()).isSuccess();
        Dict result = Dict.create();
        if (!success) {
            result.set("msg", "验证码校验失败");
            return R.fail(result);
        }
        // 校验成功后回传验证码ID
        result.set("id", body.getId());
        clearCaptchaCache(serverRequest);
        return R.ok(result);
    }

    /**
     * 清空验证码缓存
     */
    private void clearCaptchaCache(ServerRequest serverRequest) {
        String remoteIp = getRemoteIp(serverRequest);
        // 防止没有IP导致后续执行报错问题
        if (StringUtils.isBlank(remoteIp)) {
            return;
        }
        RedisUtils.deleteObject(String.join(":", CacheConstants.CAPTCHA_GET_NUMBER, remoteIp));
        RedisUtils.deleteObject(String.join(":", CacheConstants.CAPTCHA_ERROR_NUMBER, remoteIp));
    }

    /**
     * 生成验证码接口限流
     *
     * @param serverRequest 请求
     */
    @Override
    public void validateGet(ServerRequest serverRequest) {
        //1.获取请求IP
        String remoteIp = getRemoteIp(serverRequest);
        // 防止没有IP导致后续执行报错问题
        if (StringUtils.isBlank(remoteIp)) {
            return;
        }
        String key = String.join(":", CacheConstants.CAPTCHA_GET_NUMBER, remoteIp);
        if (!RedisUtils.hasKey(key)) {
            RedisUtils.setCacheObject(key, 0, Duration.ofMinutes(captchaProperties.getLockTime()));
        }
        long captchaGetNumber = RedisUtils.getAtomicValue(key);
        if (captchaProperties.getChallengeAttempts() <= captchaGetNumber) {
            throw new UserException("user.captcha.get.count", captchaProperties.getChallengeAttempts(), captchaProperties.getLockTime());
        }
        RedisUtils.incrAtomicValue(key);
    }

    /**
     * 校验验证码接口限流
     *
     * @param serverRequest 请求
     */
    @Override
    public void validateCheck(ServerRequest serverRequest) {
        String remoteIp = getRemoteIp(serverRequest);
        // 防止没有IP导致后续执行报错问题
        if (StringUtils.isBlank(remoteIp)) {
            return;
        }
        String key = String.join(":", CacheConstants.CAPTCHA_ERROR_NUMBER, remoteIp);
        if (!RedisUtils.hasKey(key)) {
            RedisUtils.setCacheObject(key, 0, Duration.ofMinutes(captchaProperties.getLockTime()));
        }
        long captchaErrorNumber = RedisUtils.getAtomicValue(key);
        if (captchaProperties.getVerificationErrorCount() <= captchaErrorNumber) {
            throw new UserException("user.captcha.error.count", captchaProperties.getVerificationErrorCount(), captchaProperties.getLockTime());
        }
        RedisUtils.incrAtomicValue(key);
    }

    /**
     * 获取请求的IP信息
     *
     * @param serverRequest 请求
     * @return IP
     */
    private String getRemoteIp(ServerRequest serverRequest) {
        ServerHttpRequest request = serverRequest.exchange().getRequest();
        InetSocketAddress remoteAddress = request.getRemoteAddress();
        if (ObjectUtil.isNull(remoteAddress)) {
            return "";
        }
        return remoteAddress.getAddress().getHostAddress();
    }
}
